package view;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/**
 * Draws a 2D line graph comparing two equal length vectors of data.
 * 
 * @author Grant Oakley
 * 
 */
@SuppressWarnings("serial")
public class LineGraph extends Visualization {

    private static final int NUMBER_OF_ALLOWED_VECTORS = 2;
    private static final int DATA_POINT_SIZE = 4;

    private List<DataPoint> myDataPoints = new ArrayList<DataPoint>();
    private List<Line> myLines = new ArrayList<Line>();

    /**
     * Default constructor with no size specification.
     */
    public LineGraph () {
        super();
    }

    /**
     * @param size sets the preferred size of the component
     */
    public LineGraph (Dimension size) {
        super(size);
    }

    @Override
    public void paint (Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        updateDataPoints();
        for (DataPoint dp : myDataPoints) {
            dp.paint(g2d);
        }
        g2d.setColor(getColor());
        for (Line l : myLines) {
            l.paint(g2d);
        }
        paint2DAxisAndLabels(g2d);
    }

    @Override
    public boolean isCorrectNumberOfDataVectors (int numberOfVectors) {
        return numberOfVectors == NUMBER_OF_ALLOWED_VECTORS;
    }

    @Override
    protected boolean dataVectorDimensionAgreement (List<?>[] vectors) {
        int acceptedLength = vectors[0].size();
        for (List<?> l : vectors) {
            if (l.size() != acceptedLength) { return false; }
        }
        return true;
    }

    /**
     * Matches the elements of the two data vectors and scales their values to
     * the size of the plotting area. These values are used to instantiate
     * DataPoints and Lines.
     */
    private void updateDataPoints () {
        DataVector<? extends Comparable<?>> xData = getAxis(Visualization.X_AXIS);
        DataVector<? extends Comparable<?>> yData = getAxis(Visualization.Y_AXIS);

        float xDataMin = xData.getRangeLowerBound();
        float xDataMax = xData.getRangeUpperBound();
        float yDataMin = yData.getRangeLowerBound();
        float yDataMax = yData.getRangeUpperBound();

        myDataPoints.clear();
        for (int i = 0; i < xData.size(); i++) {
            if (xData.elementIsWithinRange(i)) {
                float scaledToRangeX = mapToRange(xData.getAsFloat(i), xDataMin, xDataMax, 0, 1);
                float scaledToRangeY = mapToRange(yData.getAsFloat(i), yDataMin, yDataMax, 0, 1);
                float relativePositionX =
                        mapToRange(scaledToRangeX, 0, 1, getPlotBoundaryLeft(),
                                   getPlotBoundaryRight());
                float relativePositionY =
                        mapToRange(scaledToRangeY, 0, 1, getPlotBoundaryBottom(),
                                   getPlotBoundaryTop());
                myDataPoints.add(new DataPoint(relativePositionX, relativePositionY));
            }
        }
        Collections.sort(myDataPoints);

        myLines.clear();
        for (int i = 0; i < myDataPoints.size() - 1; i++) {
            myLines.add(new Line(myDataPoints.get(i), myDataPoints.get(i + 1)));
        }
    }

    /**
     * Stores correlated elements of the data vectors and their relative
     * position on the plotting area.
     * 
     * @author geo4
     * 
     */
    private class DataPoint implements Comparable<DataPoint> {
        private float myRelativeXPosition;
        private float myRelativeYPosition;

        public DataPoint (float x, float y) {
            myRelativeXPosition = x;
            myRelativeYPosition = y;
        }

        public void paint (Graphics g) {
            g.setColor(getColor());
            g.fillOval(getXPosition() - (int) ((float) DATA_POINT_SIZE / 2),
                       getYPosition() - (int) ((float) DATA_POINT_SIZE / 2), DATA_POINT_SIZE,
                       DATA_POINT_SIZE);
        }

        public int getXPosition () {
            return (int) (myRelativeXPosition * getSize().width);
        }

        public int getYPosition () {
            return (int) (myRelativeYPosition * getSize().height);
        }

        public float getRelativeXPosition () {
            return myRelativeXPosition;
        }

        @Override
        public int compareTo (DataPoint other) {
            if (myRelativeXPosition < other.getRelativeXPosition()) {
                return 1;
            }
            else if (myRelativeXPosition > other.getRelativeXPosition()) { return -1; }

            return 0;
        }

    }

    /**
     * Connects two DataPoint objects. This class is useful in the case that the
     * rendering of lines between DataPoints needs to be changed.
     * 
     * @author geo4
     * 
     */
    private class Line {
        private DataPoint myStart;
        private DataPoint myEnd;

        public Line (DataPoint start, DataPoint end) {
            myStart = start;
            myEnd = end;
        }

        public void paint (Graphics g) {
            g.drawLine(myStart.getXPosition(), myStart.getYPosition(), myEnd.getXPosition(),
                       myEnd.getYPosition());
        }
    }

}
